#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
@Time   : 2019/11/26 12:54
@Author : LeeCQ
@File Name: __init__.py

更多操作详见 下方import 内容。

"""

import pymysql
import sys
from sql_error import *


class MyMySQL:
    """MySQL 操作的模板：

    这个类包含了最基本的MySQL数据库操作，SELECT, INSERT, UPDATE, DELETE

    主要的接口方法：
    :method _select(self, table, column_name, *args, **kwargs):
            从数据库查询数据： - 表名，查询列，拓展查询列（可以使用通配符'*'）
                             - kwargs: {WHERE, LIMIT, OFFSET}  LIMIT 有一种新用法 - limit 偏移量，限制值
        :return 查找的数据

    :method _insert(self, table, **kwargs)
            向数据库插入新数据 - kwargs: 需要插入的数据的 键值对；
        :return 0 or -1

    :method _update(self, table, where_key, where_value, **kwargs)
            向数据库更新字段的值，  - where代表着查询的行  -- 如果没有将会更新所有行。
                                 - kwargs 插入的键值对
        :return 0 or -1

    :method _drop(self, option, name)：
            删除数据表或者数据库      - option = table or database
                                   - name 对应的名称
        :return 0 or -1

    :method _delete(self, table, where_key, where_value, **kwargs):

    :method write_db(self, command):
            没有模板时使用     - command: 数据库查询字符串。
        :return 0 or -1

    :method read_db(self, command):
                没有模板时使用     - command: 数据库查询字符串。
        :return 查询结果；


    :param str host:    链接的数据库主机；
    :param int port:    数据库服务器端口
    :param str user:    数据库用户名
    :param str passwd:  数据库密码
    :param str db:      数据库的DataBase
    :param str charset: 数据库的字符集
    :param str prefix:  表前缀
    """
    def __init__(self, host, port, user, passwd, db, charset, **kwargs):
        self.SQL_HOST = host  # 主机
        self.SQL_PORT = port  # 端口
        self.SQL_USER = user  # 用户
        self.SQL_PASSWD = passwd  # 密码
        self.SQL_DB = db  # 数据库
        self.SQL_CHARSET = charset  # 编码
        # 表前缀
        self.TABLE_PREFIX = '' if 'prefix' not in kwargs.keys() else kwargs['prefix']
        self._sql = pymysql.connect(host=self.SQL_HOST,
                                    port=self.SQL_PORT,
                                    user=self.SQL_USER,
                                    password=self.SQL_PASSWD,  # 可以用 passwd为别名
                                    database=self.SQL_DB,      # 可以用 db    为别名；
                                    charset=self.SQL_CHARSET
                                    )

    def set_prefix(self, prefix):
        self.TABLE_PREFIX = prefix

    def test_connect(self):
        """测试链接状况"""
        print(self.__read_db('show tables'))

    def __write_db(self, command, args=None):
        """执行数据库写入操作

        :type args: str, list or tuple
        """
        # print(self.__write_db.__name__, args)
        cur = self._sql.cursor()  # 使用cursor()方法获取操作游标
        try:
            cur.execute(command, args)
            self._sql.commit()  # 提交数据库
            cur.close()  # 释放游标
            return 0
        except:
            self._sql.rollback()
            cur.close()
            sys.exc_info()
            raise MyMySqlWriteError('操作数据库时出现问题，数据库已回滚至操作前——\n' + sys.exc_info())

    def __write_rows(self, command, args):
        """向数据库写入多行"""
        try:
            with self._sql.cursor() as cur:
                cur.executemany(command, args)
                self._sql.commit()
            return 0
        except:
            self._sql.rollback()
            sys.exc_info()
            raise MyMySqlWriteError("__write_rows() 操作数据库出错，已回滚 \n"+sys.exc_info())

    def __read_db(self, command, args=None):
        """执行数据库读取数据， 返回结果"""
        cur = self._sql.cursor()
        cur.execute(command, args)
        results = cur.fetchall()
        cur.close()
        return results

    def __columns(self, table):
        """返回table中列（字段）的所有信息"""
        return self.__read_db(f'show columns from `{table}`')

    def __columns_name(self, table):
        """返回 table 中的 列名在一个列表中"""
        return [_c[0] for _c in self.__columns(table)]

    def __tables_name(self):
        """由于链接时已经指定数据库，无需再次指定。返回数据库中所有表的名字。"""
        return [_c[0] for _c in self.__read_db("show tables")]

    def __key_and_table_is_exists(self, table, key, *args, **kwargs):
        """ 判断 key & table 是否存在

        :param table: 前缀 + 表单名
        :param key, args: 键名
        :param kwargs: 键名=键值；
        :return: 0 存在
        """
        if table not in self.__tables_name():
            raise MyMySqlTableNameError(f"{table} NOT in This Database: {self.SQL_DB};\n"
                                        f"(ALL tables {self.__tables_name()}")
        cols = self.__columns_name(table)

        not_in_table_keys = [k for k, v in kwargs.items() if k not in cols]
        not_in_table_keys += [k for k in args if k not in cols]
        if key not in cols and not_in_table_keys:
            raise MyMySqlKeyNameError(f'The key {not_in_table_keys} NOT in this Table: {table};\n'
                                      f'(ALL Columns {cols})')
        return 0

    @staticmethod
    def __insert_zip(values):
        """一次向插入数据库多条数据时，打包相应的数据。"""
        for x in values:
            if not isinstance(x, (tuple, list)):
                raise MyMySqlInsertZipError(f"INSERT多条数据时，出现非列表列！确保数据都是list或者tuple。\n错误的值是：{x}")

            if not len(values[0]) == len(x):
                raise MyMySqlInsertZipError(f'INSERT多条数据时，元组长度不整齐！请确保所有列的长度一致！\n'
                                            f'[0号]{len(values[0])}-[{values.index(x)}号]{len(x)}')

        return tuple([v for v in zip(*values)])  # important *

    def _create_table(self, command, *args):
        with self._sql.cursor() as cur:
            cur.execute(command, args)
            self._sql.commit()
        return 0

    def _insert(self, table, **kwargs):
        """ 向数据库插入内容。

        :param table: 表名；
        :param kwargs: 字段名 = 值；
        :return:
        """
        _c = (f"INSERT INTO `{self.TABLE_PREFIX}{table}`  "
              "( " +
              ', '.join([" `" + _k + "` " for _k in kwargs.keys()]) +
              " ) "         # 这一行放在后面会发生，乱版；
              " VALUES "
              " ( " + ', '.join([" %s " for _k in kwargs.values()]) + " ) ; "  # 添加值
              )
        # print(self._insert.__name__, _c)
        if not isinstance(list(kwargs.values())[0], str):
            arg = self.__insert_zip(tuple(kwargs.values()))
            return self.__write_rows(_c, arg)
        else:
            for x in kwargs.values():
                if not isinstance(x, (int, str)):
                    raise MyMySqlInsertZipError("INSERT一条数据时，出现列表列或元组！确保数据统一")
            return self.__write_db(_c, list(kwargs.values()))  # 提交

    def _select(self, table, column_name, *args, **kwargs):
        """ select的应用。

            ·· `就不再支持 * `
            ·· column_name, table, key 可以用 ` ` 包裹， value 一定不能用， value 用 ' ' 。
        :param table:
        :param column_name: 传参时自行使用 `` , 尤其是数字开头的参数
        :param args: column_name 的拓展
        :param kwargs: {'WHERE', 'LIMIT', 'OFFSET'} 全大写
        :return 结果集
        """
        command = f"SELECT {column_name} "
        for _a in args:
            command += f" , {_a} "
        command += f'FROM `{self.TABLE_PREFIX}{table}` '
        for key, value in kwargs.items():
            key = key.upper()
            if key in ['WHERE', 'LIMIT', 'OFFSET']:
                command += f' {key}  {value}'

        return self.__read_db(command)

    def _update(self, table, where_key, where_value, **kwargs):
        """ 更新数据库

        :param table: 数据表名字
        :param where_key: where的键
        :param where_value: where的值
        :param kwargs: 更新的键 = 更新的值， 注意大小写，数字键要加 - ``
        :return: 0 成功。
        """
        self.__key_and_table_is_exists(self.TABLE_PREFIX+table, where_key)  # 判断 表 & 键 的存在性！

        command = f"UPDATE `{self.TABLE_PREFIX}{table}` SET  "
        for k, v in kwargs.items():
            command += f", {k}='{v}'  "
        command += r"WHERE {}='{}';".format(where_key, where_value)

        return self.__write_db(command)  # 执行SQL语句

    def _drop(self, option, name):
        """ 删除数据库内容：

        :param option: (TABLE or DATABASE)
        :param name:
        :return: 0 成功
        """
        command = f'DROP  {option}  `{name}`'
        return self.__write_db(command)

    def _delete(self, table, where_key, where_value, **kwargs):
        """删除数据表中的某一行数据，
        :param table:
        :param where_key: 最好是数据库的主键或唯一的键。如果数据库没有，则最好组合where，以保证删除 - 唯一行。
        :param where_value:
        :param kwargs: 键名=键值；where——key的补充。
        """
        self.__key_and_table_is_exists(self.TABLE_PREFIX+table, where_key, **kwargs)

        command = f"DELETE FROM `{self.TABLE_PREFIX}{table}` WHERE {where_key}='{where_value}'  "
        for k, v in kwargs.items():
            command += f"{k}='{v}'"

        return self.__write_db(command)

    def write_db(self, command, *args):
        return self.__write_db(command, *args)

    def read_db(self, command):
        return self.__read_db(command)

    def test_show(self):
        return self.__read_db('show tables')


class MyMySqlAPI(MyMySQL):
    def __init__(self, host, port, user, passwd, db, charset, **kwargs):
        super().__init__(host, port, user, passwd, db, charset, **kwargs)

    def create_table(self, command, *args):
        return self._create_table(command, *args)

    def insert(self, table, **kwargs):
        return self._insert(table, **kwargs)

    def select(self, table, column_name, *args, **kwargs):
        return self._select(table, column_name, *args, **kwargs)

    def update(self, table, where_key, where_value, **kwargs):
        return self._update(table, where_key, where_value, **kwargs)

    def drop(self, option, name):
        return self._drop(option, name)

    def delete(self, table, where_key, where_value, **kwargs):
        return self._delete(table, where_key, where_value, **kwargs)


if __name__ == '__main__':
    a = MyMySQL('t.sql.leecq.xyz', 62715, 'Mzitu', 'Mzitu123456', 'Mzitu', 'utf8')
    # a._select('Mzitu_no_json', '*', LIMIT='10')
    # print(a._update('Mzitu_no_json', 'a_id', '123'))
    print(a.test_show())
